/*   Copyright (C) 2008  Jeroen v. Schagen & Youri Lima
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Drawing;
using System.Windows.Forms;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace Engine.Rendering
{
    // RenderWindow
    public class RenderWindow : Form
    {
        // Attributes
        protected Device device;
        protected Camera camera;

        protected Terrain terrain;
        protected Skybox skybox;

        protected static Mutex mutex = new Mutex();

        // Should be in device wrapper?
        protected ArrayList renderables;
        protected ArrayList debugrenders;

        // Wrong class?
        protected Microsoft.DirectX.Direct3D.Font text;
        protected int lastTick = 0;

        // Constructor
        public RenderWindow(String title)
        {
            this.Text = title;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque , true);

            renderables = new ArrayList();
            debugrenders = new ArrayList();
        }

        // Setup device
        public bool InitDevice(int width, int height, bool fullscreen)
        {
            try
            {
                this.Width = width;
                this.Height = height;
                this.Size = new System.Drawing.Size(width, height);

                // Setup present parameters
                PresentParameters presentParams = new PresentParameters();
                presentParams.Windowed = !fullscreen;

                if (fullscreen)
                {
                    presentParams.BackBufferWidth = width;
                    presentParams.BackBufferHeight = height;
                    presentParams.BackBufferFormat = Format.R5G6B5;
                }

                presentParams.SwapEffect = SwapEffect.Discard;
                presentParams.AutoDepthStencilFormat = DepthFormat.D24S8;
                presentParams.EnableAutoDepthStencil = true;
                
                // Setup device
                try
                {
                    device = new Device(0, DeviceType.Hardware, this, CreateFlags.HardwareVertexProcessing, presentParams);
                }

                catch (Exception)
                {
                    device = new Device(0, DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
                }

                device.Transform.Projection = Matrix.PerspectiveFovLH((float)System.Math.PI / 4, ((float)this.Width) / ((float)this.Height), 1f, 2000f);
                device.RenderState.Lighting = true;

                device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
                device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;

                device.SamplerState[0].AddressU = TextureAddress.Mirror;
                device.SamplerState[0].AddressV = TextureAddress.Mirror;

                // Setup font
                System.Drawing.Font font = new System.Drawing.Font("Arial", 12f, FontStyle.Regular);
                text = new Microsoft.DirectX.Direct3D.Font(device, font);
                
                // Setup meshmanager
                MeshManager.Instance.Init(device);

                // Setup callbacks
                device.DeviceReset += new EventHandler(this.OnDeviceReset);
                
                return true;
            }

            catch (DirectXException)
            {
                return false;
            }
        }

        public void AddRenderable(IRenderable renderable)
        {
            mutex.WaitOne();
            if (! renderables.Contains(renderable))
            {
                // Initiate renderable
                renderable.Init(device);

                // Add to render list
                renderables.Add(renderable);
            }
            mutex.ReleaseMutex();
        }

        [System.Diagnostics.Conditional("DEBUG")]
        public void AddDiagnostic(Object debugobject)
        {
            if (! debugrenders.Contains(debugobject))
            {
                // Add to debug list
                debugrenders.Add(debugobject);
            }
        }

        [System.Diagnostics.Conditional("DEBUG")]
        private void DrawDebugInfo()
        {
            int pos = (int) 430;

            // Display FPS
            text.DrawText(null, string.Format("Framerate : {0:0.00} fps", (1000.0f / (Environment.TickCount - lastTick))), new Point(10, pos), Color.Red);         
            
            // Display debug information
            foreach (Object n in debugrenders)
            {
                if(n != null)
                    text.DrawText(null, n.ToString(), new Point(10, (pos -= 15)), Color.Red);
            }
            
            lastTick = Environment.TickCount;
        }

        // Device callback
        public virtual void OnDeviceReset(object sender, EventArgs e)
        {
            // Retrieve device
            device = sender as Device;
            // Maintain ratio
            this.Size = new System.Drawing.Size(this.Width, this.Height);
            device.Transform.Projection = Matrix.PerspectiveFovLH((float)System.Math.PI / 4, ((float)this.Width) / ((float)this.Height), 1f, 2000f);
        }

        // Paint callback
        protected override void OnPaint(PaintEventArgs e)
        {
            // Begin scene
            device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, System.Drawing.Color.Black, 4.0f, 0);
            device.BeginScene();

            // Handle view
            if (camera != null) 
            {
                camera.Display(device, this.Width, this.Height);
            }

            // Render terrain
            if (terrain != null) 
            {
                terrain.Render(device, camera);
            }

            // Render skybox
            if (skybox != null)
            {
                skybox.Render(device, camera);
            }

            mutex.WaitOne();
            // Render renderables
            foreach (IRenderable r in renderables)
            {
                r.Render(device, camera);
            }
            mutex.ReleaseMutex();

            // Draw debug information
            DrawDebugInfo();

            // End scene.
            device.EndScene();
            device.Present();

            this.Invalidate();
        }

        // Paint background callback
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            // Do nothing to prevent flickering
        }

        // Size change callback
        protected override void OnSizeChanged(EventArgs e)
        {
            this.Invalidate();
            if(device!=null)device.Transform.Projection = Matrix.PerspectiveFovLH((float) System.Math.PI / 4, ((float) this.Width) / ((float) this.Height), 1f, 2000f);
        }

        // Accessors
        public Device Device
        {
            get { return device; }
        }

        public Camera Camera
        {
            get { return camera; }
            set { camera = value; }
        }

        public Terrain Terrain
        {
            get { return terrain; }
            set { terrain = value; }
        }

        public Skybox Skybox
        {
            get { return skybox; }
            set { skybox = value; }
        }

        public override string ToString()
        {
            return base.ToString() + ":" + this.Height + "," + this.Width;
        }
    }
}
